Debugging Commands
When testing and debugging a work of interactive fiction it is sometimes useful to be able to make use of a set of speoial commands not available to normal players of your game. These special commands are known as debugging commands and should only be included in the debug build of your game, not the release build. This section describes the built-in debugging commands and briefly discusses how you might add your own.
Built-In Debugging Commands
The following debugging commands are included in the adv3Lite library. They are only included in your game when you compile for debugging, and not when you compile for release.
PURLOIN: The command PURLOIN FOO (which can be abbreviated to PN FOO) can be used to add any object to the player's inventory; e.g. PURLOIN BRASS KEY causes the brass key to jump magically into the player's possession from wherever the brass key is in the game world. The PURLOIN command does impose certain sanity checks, so that, for example, you can't purloin yourself, or a room, or anything that currently contains the player character, but you can purloin things that would normally be fixed in place.
GONEAR: The command GONEAR FOO (which can be abbreviated to GN FOO or spelt as GO NEAR FOO) teleports the player character to FOO. If FOO is a room the player character is taken to that room, otherwise the player character is taken to the Room that encloses FOO. If FOO is off-stage (i.e. if its location is nil) the command is not allowed to go ahead. If FOO is a multiloc the destination the player character is taken to may not be well defined.
FIAT LUX: The command FIAT LUX (or alternatively LET THERE BE LIGHT) causes the player character to light up so s/he can see in an otherwise dark place. Repeating the command toggles the illumination off again.
DEBUG: The command DEBUG simply breaks into the debugger.
DEBUG ACTIONS: The command DEBUG ACTIONS toggles the display of debugging information about actions on and off. The information displayed is simply the name of the action (e.g. PutOn) followed by the names of any objects, topics or literals involved in the action, separated by colons. The information is displayed at the start of the action execution cycle.
DEBUG DOERS: The command DEBUG DOERS toggles the display of debugging information about doers on and off. The information displayed is simply the cmd string of the Doer (e.g. 'put Thing in casket') . The information is displayed just before Doer.exec() is called.
DEBUG MESSAGES: The command DEBUG MESSAGES toggles the display of debugging information about messages on and off. The information displayed is the id and pre-processed string of any messages output via DMsg() or BMsg() that have a non-empty id. This may be helpful in identifying where the library is getting many of its default messages from.
DEBUG SPELLING: The command DEBUG SPELLING simply toggles the display of information about how long the spelling corrector took to make a correction.
DEBUG STOP or DEBUG OFF: These commands are synonymous and simply turn all the other debugging options (the three above) off at once.
DEBUG STATUS: the command DEBUG STATUS lists whether the various debugging options are currently on or off.
TEST XXX: the TEST command can be used to run test scripts, on which see further below.
EVAL: The command EVAL expression (where expression is any valid TADS 3 expression) evaluates the expression and displays the result. If the expression evaluates to an object EVAL diplays the name property of the object (if it has one) together with its superclass list. For example:
>eval 2 + 5 7 >eval me.location hall [Room, ShuffledEventList] >eval hall.contents you [Thing],George [Actor],red ball [Thing],front door [Door],pictures [Thing],stairs [Thing],dial [NumberedDial], floor [MultiLoc,Decoration],ceiling [MultiLoc,Decoration] >eval frontDoor.isLocked true >open front door The front door is locked. >eval frontDoor.makeLocked(nil) nil >open front door You open the front door.
The final example of EVAL above illustrates that it is perfectly possible to use the EVAL command with an expression that changes the game state.
Defining New Debugging Commands
If you want to define additional debugging commands for your particular game, you can do so using the same means as described in the section on Defining New Actions above, with a couple of extra steps:
- Enclose the complete definition between #ifdef __DEBUG and #endif preprocessor commands to ensure that your custom debugging commands are included only when you compiple for debugging.
- Ensure your action definition allows for universal scope if it needs to be able to act on any item in the game (and not just those that would normally be available to the player character).
For example, if in addition to the PURLOIN command which magically transports objects into the player character's inventory, we'd like to have a SUMMON command which can summon any object into the player character's presence (i.e. the same room as the player character) we could do it like this:
#ifdef __DEBUG
VerbRule(Summon)
'summon' multiDobj
: VerbProduction
action = Summon
verbPhrase = 'summon/summoning (what)'
missingQ = 'what do you want to summon'
;
DefineTAction(Summon)
addExtraScopeItems(role) { makeScopeUniversal(); }
beforeAction() { }
afterAction() { }
turnSequence() { }
;
modify Thing
dobjFor(Summon)
{
verify()
{
if(isIn(gActor.getOutermostRoom))
illogicalNow('{The subj dobj} {is} already here. ' );
}
action()
{
moveInto(gActor.getOutermostRoom);
}
report()
{
"\^<<gActionListStr>> appears before you! ";
}
}
;
#endif
Note that since we probably don't want our debugging action to count as normal turn we override beforeAction(), afterAction() and turnSequence() on Summon to do nothing, thereby suppressing the before and after notifications, daemons and advancing the turn count.(I'm assuming that since this is a debugging command the fact that 'appears' may not agree with its subject is not too much of problem, but if it bothers you correcting it is left as an exercise for the reader.)
Basic Test Scripts
For the full story on Test Scripts see on Enhanced Test Scripts below. In this section we shall just cover the basics.
When testing a game it's often useful to be able to run a set of commands to test a particular feature. In adv3Lite you can set up named test scripts to help automate this task. To define a test script, define an object of the Test class (it can be an anonymous object). Use the testName property to give the test script a name by which it can be referred to with a TEST command. Then define the list of commands to be performed by the test script in the testList property. For example:
Test
testName = 'foo'
testList = ['x me', 'i', 'look']
;
With this definition in place you can use the command TEST FOO to carry out X ME followed by I followed by LOOK. You can also abbreviate the definition of the Test object by using the built-in template, so:
Test 'foo' ['x me', 'i', 'look'];
For some scripts to work as required it may be necessary for the actor to be in a particular location, or to have particular items in his/her inventory. For example, to have the player character unlock the golden door with the diamond key and then enter the throne room of the mystic queen, you may first need to move the player character to the tulip passage and ensure that the right key is in his inventory. The player character may also need to have the yellow rose in his inventory if he is then to present it to the mystic queen. We could set up these conditions by using gonear and purloin commands in the script, but we can also do so using the location and testHolding properties of our Test object, thus:
Test testName = 'queen' testList = ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen'] location = tulipPassage testHolding = [diamondKey, yellowRose] ;
Again, this may be abbreviated via use of the template to:
Test 'queen' ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen'] @tulipPassage [diamondKey, yellowRose] ;
The command TEST QUEEN will then move the player character to tulipPassage, move the diamondKey and the yellowRose into his inventory, and then execute the defined sequence of commands. By default a room description will be displayed following the move, but if you wish you can disable this by setting the reportMove property of the Test to nil (it's true by default to remind you of the effect of the TEST command). Similarly, by default the TEST command will notify you of items it moves into the player character's inventory, but this notification can be suppressed by setting the reportHolding property to nil.
You can remind yourself of what Tests you've defined in your game by using the LIST TESTS command. The command LIST TESTS FULLY also shows the list of commands associated with each test name.
Finally, the TEST class and its associated actions are only defined in a debug build, so you need to make sure that you surround any TEST definitions with #ifdef _DEBUG and #endif so that they don't cause compilation errors in a release build; for example:
#ifdef __DEBUG Test 'foo' ['x me', 'i', 'look']; Test 'queen' ['unlock golden door with diamond key', 'n', 'x queen', 'give yellow rose to queen'] @tulipPassage [diamondKey, yellowRose] ; #endif
Enhanced Test Scripts (with Assertions)
Specification
Thanks to work by Mitchell Mlinar, the Basic Test Script functionality described above has now been substantially enchanced, not least to incorporate assertion-based testing. This now supports the following features:
- Ability to define a set of commands to run in order.
- Ability to set up a test with the player in a specific location and specific objects in their possession.
- Ability to restore the game to the game state it was prior to the test running.
- Ability to effectively restart the game prior to the test running.
- Ability to actually test the outcome of commands such as location of the player, location of objects and even what messages were sent to the console; these will all start with the phrase assertXXX. An assertXXX will succeed or fail.
- When running a test, have it fail when an assertXXX fails.
- When running all tests, ability to STOP testing when a test fails.
- When running all tests (the norm), collect the results from the tests and give a brief summary report.
Test definition
NewTest 'testName' ['cmd1','cmd2',...] [list of objects to purloin] @location where both the [list of objects to purloin] and @location (where the player character is moved first) are optional. Here is where the similarity ends. There are five flags for controlling how the test will run including two that were there before:
- reportHolding: Report any items that were picked up. true by default.
- reportMove: Report any change in location. true by default.
- restoreStartStateAfterTest: Restores game to the state it was prior to running this test; nil by default.
- restartBeforeTest: Restarts the game from scratch prior to running this test. nil by default.
- clearAssertBufferBeforeCmd: Clear assertMsg buffer prior to each command (see below for more information); true by default.
- testOrder: The TESTALL command (see below) will run all the tests defined in your game in ascending order of their
testOrderproperty, which defaults tosourceTextOrderso that tests defined in the same source file should run in the order they're defined in that file. Game code can override this property if some other order is desired.
Assert-based test commands
All of these commands are intended to be inserted into your test command list and validate some condition. If the condition is true, the test continues. If the condition is false, a report is made to the console and that test fails and will not execute any more commands. (This is where the value of restoring game state or even restarting the game are important for testing.) Whether subsequent tests are executed or not depends upon the testall <option>. (NOTE: None of these commands advance the game time/counter but are instead considered system commands.)
assertMsg jumped over the rock.clearAssertBufferBeforeCmd = nil. It is a test no-op.oroom: Room 'outdoor room' is referred to as oroom NOT outdoor room. Example: assert me.isIn(bedquilt). Finally, the quotes are usually optional but there is one situation where the expression will result in a game error without quotes and that is when you want to test the inverse as in assert !ring.isWornBy(me). Here, you instead must use quotes: assert "!ring.isWornBy(me)";assertPlayerInRoom long hallway.assertPlayerHasItem red ball.assertPlayerLacksItem blue ball.assertPlayerRoomHasItem red ball.Example: assertPlayerRoomLacksItem blue ball.To use any of these assertXXX commands you simply include them in your Test definition like any other commands it may include, e.g.:
Test 'test1' ['east', 'get red ball', 'assertPlayerHasItem red ball'] ;
How to run your test(s)
There are several commands for running your tests:
=========================== =========================== Total tests: 16 Total asserts: 26 Failed asserts: 0 ===========================
Finally, don't forget to enclose your Test definitions in a #ifdef __DEBUG ... #endif block, e.g.:
#ifdef __DEBUG
Test 'test1' ['east', 'get red ball', 'assertPlayerHasItem red ball', 'jump']
;
Test 'test2' ['drop red ball', 'throw red ball at blue ball', 'get balls', 'i',
'assert "blueBall.isIn(me)"'] [bugle]
restoreStartStateAfterTest = true
;
Test 'test3' ['i', 's','t me',
'assertMsg i am someone'] @startroom
restartBeforeTest = true
;
Test 'test123' ['test test1', 'test test2', 'test test3'];
#endif
Additional Debugging Resources
The adv3Library has one or two built-in checks to help with various kinds of common situations. For example, when writing conversational responses it's very easy to mismatch the smart quote tags <q> and </q>; the adv3Lite thus looks out for any smart quotes that are mismatched over the course of a single turn and displays a warning whenever mismatched smart quotes are displayed, so the game author can correct them (it also tries to prevent the effect of mismatched smart quotes propagating to successive turns). If you want to suppress the warning messages in the released version of your game you can override quoteFilter.showWarnings to nil; however, you might find it useful to leave the warnings on for versions of the game you sent to beta-testers, which is why this isn't simply tied to whether or not you compiled for debugging.
When your beta-testers test your game, it is often helpful to get them to record a transcript of it (using the SCRIPT command), in the course of which they can type comments such as:
>* TYPO elehpant -> elephant ... >* BUG! The brass key won't work on the inside of the tower door
By default such comments are commands that start with an asterisk (*). To change how comments should be prefixed, see the discussion of the commentPreParser.
